﻿#region Using

using System;
using System.Collections.Generic;
using System.Linq;

using Common.Base;

using GameLogic.External;

#endregion

namespace GameLogic.Internal
{
    internal class Game : IGameState, IGame, ICommandExecutor
    {
        private readonly FarmersBreakfastHexagonBoard gameBoard;

        private readonly object lockObject = new object();

        private readonly List<Player> players;

        private readonly List<Route> routes;

        private readonly GameTimeManager timeManager;

        internal Game(IList<Player> argPlayers)
        {
            this.players = new List<Player>(argPlayers);
            this.gameBoard = new FarmersBreakfastHexagonBoard(new Size(4, 4), (uint)argPlayers.Count);
            this.routes = new List<Route>();

            foreach (var tile in this.gameBoard)
            {
                tile.Position = this.gameBoard.GetIndex(tile);

                foreach (var neighbor in this.gameBoard.GetNeighbors(tile))
                {
                    this.routes.Add(new Route(tile, neighbor));
                }
            }

            var playerStack = new Stack<Player>(argPlayers);

            foreach (var tile in this.gameBoard.GetStartingTiles())
            {
                var player = playerStack.Pop();
                player.HomeTile = tile;
                tile.DeclareAsStartingTileFor(player);
            }

            this.timeManager = new GameTimeManager();
            this.timeManager.OnUpdate += this.Update;
        }

        internal IGameState CurrentState
        {
            get
            {
                return this;
            }
        }

        public event EventHandler AfterUpdate;

        public Result Execute(ICommand cmd)
        {
            lock (this.lockObject)
            {
                return cmd.ExecuteOn(this);
            }
        }

        public Result ExecuteCommand(BuildCommand cmd)
        {
            var tile = this.gameBoard.GetTile(cmd.Position);

            if (!tile.Owner.Equals(cmd.Player))
            {
                return Result.Failure("You don't own this tile!");
            }

            return tile.RaiseBuilding(cmd.BuildingType);
        }

        public Result ExecuteCommand(FarmerRouteAddCommand cmd)
        {
            if (cmd.StartPosition.Equals(cmd.EndPosition))
            {
                return Result.Failure("You must choose two different tiles!");
            }

            var startTile = this.gameBoard.GetTile(cmd.StartPosition);
            var endTile = this.gameBoard.GetTile(cmd.EndPosition);

            if (this.gameBoard.GetNeighbors(startTile).All(x => !x.Equals(endTile)))
            {
                return Result.Failure("The tiles are not neighbors!");
            }

            if (!startTile.Owner.Equals(cmd.Player) || !endTile.Owner.Equals(cmd.Player))
            {
                return Result.Failure("You don't one or both the tiles!");
            }

            var backRoute = this.routes.Find(x => x.StartTile.Equals(endTile) && x.EndTile.Equals(startTile));
            if (endTile.ContainsFarmerRoute(backRoute))
            {
                return Result.Failure("You can't add an instant back route!");
            }

            var route = this.routes.Find(x => x.StartTile.Equals(startTile) && x.EndTile.Equals(endTile));

            return startTile.AddFarmerRoute(route);
        }

        public Result ExecuteCommand(MilitiaRouteAddCommand cmd)
        {
            if (cmd.StartPosition.Equals(cmd.EndPosition))
            {
                return Result.Failure("You must choose two different tiles!");
            }

            var startTile = this.gameBoard.GetTile(cmd.StartPosition);
            var endTile = this.gameBoard.GetTile(cmd.EndPosition);

            if (this.gameBoard.GetNeighbors(startTile).All(x => !x.Equals(endTile)))
            {
                return Result.Failure("The tiles are not neighbors!");
            }

            if (!startTile.Owner.Equals(cmd.Player))
            {
                return Result.Failure("You don't own this tile!");
            }

            var backRoute = this.routes.Find(x => x.StartTile.Equals(endTile) && x.EndTile.Equals(startTile));
            if (endTile.Owner.Equals(cmd.Player) && endTile.ContainsMilitiaRoute(backRoute))
            {
                return Result.Failure("You can't add an instant back route!");
            }

            var route = this.routes.Find(x => x.StartTile.Equals(startTile) && x.EndTile.Equals(endTile));

            return startTile.AddMilitiaRoute(route);
        }

        public Result ExecuteCommand(BuyUpgradeCommand cmd)
        {
            throw new NotImplementedException();
        }

        public Result ExecuteCommand(FarmerRouteRemoveCommand cmd)
        {
            if (cmd.StartPosition.Equals(cmd.EndPosition))
            {
                return Result.Failure("You must choose two different tiles!");
            }

            var startTile = this.gameBoard.GetTile(cmd.StartPosition);
            var endTile = this.gameBoard.GetTile(cmd.EndPosition);

            if (this.gameBoard.GetNeighbors(startTile).All(x => !x.Equals(endTile)))
            {
                return Result.Failure("The tiles are not neighbors!");
            }

            if (!startTile.Owner.Equals(cmd.Player) && !endTile.Owner.Equals(cmd.Player))
            {
                return Result.Failure("You don't own this tile!");
            }

            var route = this.routes.Find(x => x.StartTile.Equals(startTile) && x.EndTile.Equals(endTile));

            return startTile.RemoveFarmerRoute(route);
        }

        public Result ExecuteCommand(MilitiaRouteRemoveCommand cmd)
        {
            if (cmd.StartPosition.Equals(cmd.EndPosition))
            {
                return Result.Failure("You must choose two different tiles!");
            }

            var startTile = this.gameBoard.GetTile(cmd.StartPosition);
            var endTile = this.gameBoard.GetTile(cmd.EndPosition);

            if (this.gameBoard.GetNeighbors(startTile).All(x => !x.Equals(endTile)))
            {
                return Result.Failure("The tiles are not neighbors!");
            }

            if (!startTile.Owner.Equals(cmd.Player) && !endTile.Owner.Equals(cmd.Player))
            {
                return Result.Failure("You don't own this tile!");
            }

            var route = this.routes.Find(x => x.StartTile.Equals(startTile) && x.EndTile.Equals(endTile));

            return startTile.RemoveMilitiaRoute(route);
        }

        public void ManualPlayerLoose(Identifiable identifiable)
        {
            lock (this.lockObject)
            {
                foreach (var player in this.players)
                {
                    if (player.Equals(identifiable))
                    {
                        player.HomeTile.ChangeOwner(Player.NeutralPlayer);
                    }
                }
            }
        }

        public event EventHandler<PlayerEventArgs> OnGameOver;

        public event EventHandler OnPaused;

        public event EventHandler<PlayerEventArgs> OnPlayerLost;

        public event EventHandler OnResumed;

        public void Pause()
        {
            lock (this.lockObject)
            {
                this.timeManager.Pause();
                this.OnPaused(this, new EventArgs());
            }
        }

        public IEnumerable<IPlayer> Players
        {
            get
            {
                return this.players;
            }
        }

        public void Resume()
        {
            lock (this.lockObject)
            {
                this.timeManager.Resume();
                this.OnResumed(this, new EventArgs());
            }
        }

        public void Start()
        {
            this.timeManager.Start();
        }

        public IEnumerable<ITile> Tiles
        {
            get
            {
                return this.gameBoard;
            }
        }

        public void Update()
        {
            lock (this.lockObject)
            {
                this.timeManager.Update();
            }
        }

        private void Update(GameTime gameTime)
        {
            foreach (var tile in this.gameBoard)
            {
                tile.Update(gameTime);
            }

            foreach (var route in this.routes)
            {
                if (route.Enabled)
                {
                    route.Update(gameTime);
                }
            }

            foreach (var player in this.players.ToList())
            {
                if (!player.HomeTile.Owner.Equals(player))
                {
                    this.players.Remove(player);
                    this.OnPlayerLost(this, new PlayerEventArgs(player));

                    // Alle übrigen Tiles wechseln Besitzer
                    var player1 = player;
                    foreach (var tile in this.gameBoard.Where(x => x.Owner.Equals(player1)))
                    {
                        tile.ChangeOwner(player.HomeTile.Owner);
                    }

                    if (this.players.Count == 1)
                    {
                        this.OnGameOver(this, new PlayerEventArgs(this.players.First()));
                    }
                }
            }

            this.AfterUpdate(this, new EventArgs());
        }
    }
}